package com.hup.utils.swing.customView.viewTable2;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hup.utils.commons.exception.CauseException;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.swing.KeyBoardUtil;
import com.hup.utils.swing.customView.viewTable2.ViewTableColumn.NullSummaryHandler;
import com.hup.utils.swing.customView.viewTable2.ViewTableColumn.NullTextRenderer;
import com.hup.utils.swing.customView.viewTable2.ViewTableColumn.VTextRenderer;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ColorUIResource;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Vector;
import java.util.stream.Collectors;

/**
 * <pre>
 * 简单的表格;
 * 功能点:
 * .使用 {@link ViewTableColumn} 设置表格样式
 * .动态[显示,隐藏]列
 * .添加统计行
 * .动态[增加,删除]行
 *
 * 注意:
 * .必须用ScrollPane 包裹, 才会显示表头
 * .注意[modelIndex,viewIndex]的关系:排序前是一样的, {@link JTable#convertRowIndexToModel(int)};
 * .更多功能,查考旧版 {@link com.hup.utils.swing.customView.viewTable.ViewTable}
 * .不支持动态[移动/拖拽]列(需要大量改动)
 *
 * https://docs.oracle.com/javase/tutorial/uiswing/components/table.html
 * @author hugan
 * @date 2024/5/28
 */
@Slf4j
public class ViewTable<R> extends JTable {

    private static final JLabel ERR_LABEL = new JLabel();
    /**
     * 偶数行背景色
     */
    @Setter
    @Getter
    private ColorUIResource evenRowBackground = new ColorUIResource(225, 240, 255);

    //
    /**
     * 全部列,含隐藏列; 可外部使用;
     *
     * @apiNote 注意, 有隐藏列时, 需要 {@link #getColumnVisible(int)} 获取VColumnInfo
     */
    @Getter
    private List<VColumnInfo> columns;
    /**
     * 当前显示列,内部使用
     */
    private List<VColumnInfo> visibleColumns;
    private MyTableModel tableModel;
    @Getter
    private final List<R> modelData;

    //===外部功能
    /**
     * 表头菜单
     */
    @Setter
    private VHeaderMenu<R> headerMenu;
    /**
     * 数据行的[右键菜单]
     */
    @Setter
    private VRowMenu<R> rowMenu;
    /**
     * 控制单元格颜色;
     * 更新数据后, 调用 table.repaint(); 刷新界面
     */
    @Setter
    private VCellColorHandler<R> cellColorHandler;

    /**
     * 数据监听器
     */
    @Setter
    private ValueUpdateListener<R> valueUpdateListener;

    public ViewTable(Class<R> clz) {
        super();
        modelData = new ArrayList<>();
        initColumnInfo(clz);
        initTable();
        initListener();
    }

    private void initColumnInfo(Class<R> clz) {
        columns = new ArrayList<>();

        Field[] fields = clz.getDeclaredFields();
        //log.info("fields=\n{}", TextUtil.getText(fields, "\n"));
        for (Field field : fields) {
            ViewTableColumn config = field.getAnnotation(ViewTableColumn.class);
            if (config == null) continue;

            VColumnInfo column = new VColumnInfo();
            if (config.isIndexColumn() && StringUtils.isBlank(config.name())) {
                column.setName("序号");
            } else {
                column.setName(config.name());
            }
            column.setOrder(config.order());
            column.setWidth(config.width() > 0 ? config.width() : null);
            column.setFixWidth(config.fixWidth() > 0 ? config.fixWidth() : null);
            if (config.textRender() != NullTextRenderer.class) {
                //此处没做对象复用,除了占内存,没太大影响
                column.setTextRenderer(JSON.parseObject("{}", config.textRender()));
            }
            column.setEditable(config.editable() || config.editPreview());
            column.setEditPreview(config.editPreview());
            column.setHeaderAlignment(config.headerAlignment());
            column.setValueAlignment(config.valueAlignment());
            column.setPaddingX(config.paddingX());
            if (StringUtils.isNotBlank(config.headerTooltip())) {
                column.setHeaderTooltip(config.headerTooltip());
            }
            column.setIndexColumn(config.isIndexColumn());
            column.setVisible(config.visible());
            if (config.summaryHandler() != NullSummaryHandler.class) {
                column.setSummaryHandler(JSON.parseObject("{}", config.summaryHandler()));
            }
            //
            column.setFieldName(field.getName());
            column.setClz(field.getType());
            column.setField(field);
            field.setAccessible(true);
            column.setBorder(new EmptyBorder(0, config.paddingX(), 0, config.paddingX()));
            columns.add(column);
        }
        columns.sort(Comparator.comparingInt(VColumnInfo::getOrder));
        //log.info("columns=\n[{}]", TextUtil.getLineWrap(columns));

        visibleColumns = new ArrayList<>();
        visibleColumns.addAll(columns);
    }

    private void initTable() {
        tableModel = new MyTableModel();
        setModel(tableModel);

        //表头
        ViewTableHeader.initTableHeader(this, visibleColumns);

        //行高, 适配屏幕缩放比例
        setRowHeight((int) (getRowHeight() * LookThemeUtil.getScreenScale()));

        cellColorHandler = new VCellColorHandler<R>() {
            @Override
            public boolean prepareColor(R rowData, boolean isSelected, boolean hasFocus, int row, int column) {
                return false;
            }
        };

        /*
        解决奇怪bug问题:
        初始化[有隐藏列],表格展示后[动态显示列]: 窗口宽度过小时,横向滚动时,表头不能滚动到最右侧
        解决方式:
            .visibleColumns实例化时,传入全部列; initTableHeader时是全部列
            .此处再隐藏列
            .就不会有奇怪bug
         */
        long visibleCount = columns.stream()
                .filter(VColumnInfo::isVisible)
                .count();
        if (visibleCount != visibleColumns.size()) {
            onColumnVisibleUpdated();
        }
    }

    //===样式方法

    /**
     * 处理[渲染效果/文本,样式]
     */
    @SuppressWarnings("unchecked")
    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        try {
            Object value = getValueAt(row, column);//注意学习源码的 convertRowIndexToModel
            int rowIndexToModel = convertRowIndexToModel(row);
            R rowObject = modelData.get(rowIndexToModel);
            //log.info("rowIndexToModel={}, rowObject={}", rowIndexToModel, rowObject);

            VColumnInfo columnInfo = visibleColumns.get(column);
            VTextRenderer<R> textRenderer = (VTextRenderer<R>) columnInfo.getTextRenderer();//这里cast不会报错...
            if (textRenderer != null) {
                //使用 javax.swing.JTable.createDefaultRenderers中,创建的DefaultTableCellRenderer(实际是JLabel)
                renderer = (TableCellRenderer) defaultRenderersByColumnClass.get(Object.class);
                value = textRenderer.get(rowObject, value);
            }

            //=== 下文 代码来自 super.prepareRenderer()
            boolean isSelected = false;
            boolean hasFocus = false;
            if (!isPaintingForPrint()) {
                isSelected = isCellSelected(row, column);
                boolean rowIsLead = (selectionModel.getLeadSelectionIndex() == row);
                boolean colIsLead = (columnModel.getSelectionModel().getLeadSelectionIndex() == column);
                hasFocus = (rowIsLead && colIsLead) && isFocusOwner();
            }
            Component res = renderer.getTableCellRendererComponent(this, value,
                    isSelected, hasFocus,
                    row, column);

            //===下文 自定义代码
            //if (column == 4) {//debug
            //    log.info("[{},{}], value={}", column, row, value);
            //}

            //对齐方式
            if (res instanceof JLabel) {
                //noinspection MagicConstant
                ((JLabel) res).setHorizontalAlignment(columnInfo.getValueAlignment());
                ((JLabel) res).setBorder(columnInfo.getBorder());
            }

            boolean needSet = cellColorHandler.prepareColor(rowObject, isSelected, hasFocus, rowIndexToModel, column);
            if (needSet) {
                res.setForeground(cellColorHandler.getForeground());
                res.setBackground(cellColorHandler.getBackground());
            } else {
                //偶数行背景色
                if (!isSelected) {
                    if (row % 2 == 1) {
                        res.setForeground(null);
                        res.setBackground(evenRowBackground);
                    } else {
                        res.setForeground(null);
                        res.setBackground(null);
                    }
                }
            }

            return res;
        } catch (Throwable e) {
            log.error("渲染错误: loc=[{},{}]", column, row, e);
            return ERR_LABEL;
        }
    }

    /**
     * 实现"窗口偏大时,列宽自动拉伸;窗口偏小时,展示水平滚动条"
     * 需要配合:setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
     */
    @Override
    public boolean getScrollableTracksViewportWidth() {
        return getPreferredSize().width < getParent().getWidth();
    }

    //===功能方法
    private void initListener() {
        //取消选中行
        KeyBoardUtil.listen(this, (keyCode, e) -> {
            if (KeyEvent.VK_ESCAPE == keyCode) clearSelection();
        });

        //表头菜单
        getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (!SwingUtilities.isRightMouseButton(e)) return;
                if (headerMenu != null) headerMenu.mouseClicked(ViewTable.this, e);
            }
        });

        //数据行的[右键菜单]
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (!SwingUtilities.isRightMouseButton(e)) return;
                if (rowMenu != null) rowMenu.mouseClicked(ViewTable.this, e);
            }
        });
    }

    /**
     * 实现单元格更新监听,
     * 不深究 javax.swing.table.TableModel#addTableModelListener(javax.swing.event.TableModelListener)!!
     */
    @Override
    public void setValueAt(Object newValue, int row, int column) {
        if (visibleColumns.get(column).isEditPreview()) return;
        int rowIndexToModel = convertRowIndexToModel(row);
        int columnIndexToModel = convertColumnIndexToModel(column);
        TableModel model = getModel();
        Object old = model.getValueAt(rowIndexToModel, columnIndexToModel);

        if (Objects.equals(newValue, old)) {
            //双击单元格后,不改变值时,除了Esc外,其他操作都会触发到达这里!需要过滤
            //log.debug("changed same={}", old);
            return;
        }

        //[排序中]时,不会触发排序
        model.setValueAt(newValue, rowIndexToModel, columnIndexToModel);
        //log.debug("[{},{}]={} -> {}", column, row, old, newValue);

        //更新modelData
        Field field = visibleColumns.get(column).getField();
        R rowObject = modelData.get(rowIndexToModel);
        try {
            field.set(rowObject, newValue);
        } catch (IllegalAccessException e) {
            throw new CauseException("编辑异常:", e);
        }

        //触发监听器
        if (valueUpdateListener != null) {
            valueUpdateListener.afterUpdate(rowIndexToModel, columnIndexToModel, newValue, old, this);
        }
    }

    //===外部方法

    /**
     * 设置全部数据
     *
     * @apiNote 会保持现有排序;
     * 注意,尽量在UI线程调用 SwingUtilities.invokeLater; 否则 prepareRenderer 可能会有越界异常!!
     */
    public void setData(List<R> data) {
        tableModel.setData(data);
    }

    /**
     * 追加数据
     *
     * @apiNote 会保持现有排序;
     * 注意,尽量在UI线程调用 SwingUtilities.invokeLater; 否则 prepareRenderer 可能会有越界异常!!
     */
    public void addData(List<R> data) {
        ArrayList<R> list = new ArrayList<>(modelData);
        list.addAll(data);
        setData(list);
    }

    /**
     * 清空数据
     *
     * @apiNote 会保持现有排序;
     * 注意,尽量在UI线程调用 SwingUtilities.invokeLater; 否则 prepareRenderer 可能会有越界异常!!
     */
    public void clearData() {
        setData(new ArrayList<>());
    }

    /**
     * @param index modelIndex
     * @apiNote 会保持现有排序;
     * 注意,尽量在UI线程调用 SwingUtilities.invokeLater; 否则 prepareRenderer 可能会有越界异常!!
     */
    public void removeData(int index) {
        ArrayList<R> list = new ArrayList<>(modelData);
        list.remove(index);
        setData(list);
    }

    /**
     * 重置排序
     */
    public void resetSort() {
        getRowSorter().setSortKeys(null);
    }

    /**
     * @param row viewIndex
     */
    public R getData(int row) {
        return modelData.get(convertRowIndexToModel(row));
    }

    /**
     * @param row modelIndex
     */
    public R getDataOfModel(int row) {
        return modelData.get(row);
    }

    /**
     * @return nullable, 当前选中行
     */
    public R getSelectedData() {
        int row = getSelectedRow();
        if (row == -1) return null;
        return modelData.get(convertRowIndexToModel(row));
    }

    /**
     * @return viewIndex
     */
    @Override
    public int getSelectedRow() {
        return super.getSelectedRow();
    }

    /**
     * @return modelIndex
     */
    public int getSelectedRowOfModel() {
        return convertRowIndexToModel(super.getSelectedRow());
    }

    /**
     * @return viewIndex
     */
    @Override
    public int[] getSelectedRows() {
        return super.getSelectedRows();
    }

    /**
     * @return modelIndex
     */
    public int[] getSelectedRowsOfModel() {
        int[] selectedRows = super.getSelectedRows();
        for (int i = selectedRows.length - 1; i >= 0; i--) {
            //处理排序后index转化
            selectedRows[i] = convertRowIndexToModel(selectedRows[i]);
        }
        return selectedRows;
    }

    /**
     * 根据colIndex 获取 VColumnInfo
     */
    public VColumnInfo getColumnVisible(int col) {
        return visibleColumns.get(col);
    }

    /**
     * 重置列宽
     */
    public void resetColumnWidth() {
        float screenScale = LookThemeUtil.getScreenScale();
        TableColumnModel columnModel = getColumnModel();
        for (int i = 0; i < columnModel.getColumnCount(); i++) {
            TableColumn tableColumn = columnModel.getColumn(i);

            VColumnInfo columnInfo = visibleColumns.get(i);
            Integer fixWidth = columnInfo.getFixWidth();
            Integer width = columnInfo.getWidth();
            if (fixWidth != null) {
                //log.info("column={}, fixWidth={}", columnInfo.getName(), fixWidth);
                int value = (int) (fixWidth * screenScale);
                tableColumn.setMinWidth(value);
                tableColumn.setMaxWidth(value);
            } else if (width != null) {
                //log.info("{}.width={}", i, width);
                tableColumn.setPreferredWidth((int) (width * screenScale));
            }
            //log.info("name={}, {}, [{}, {}], {}", tableColumn.getHeaderValue(), tableColumn.getWidth(),
            //        tableColumn.getMinWidth(), tableColumn.getMaxWidth(), tableColumn.getPreferredWidth());
        }
    }

    /**
     * onColumnVisibleUpdated
     */
    public void onColumnVisibleUpdated() {
        visibleColumns.clear();
        visibleColumns.addAll(columns.stream()
                .filter(VColumnInfo::isVisible)
                .collect(Collectors.toList()));

        Vector<String> names = visibleColumns.stream()
                .map(VColumnInfo::getName)
                .collect(Collectors.toCollection(Vector::new));
        //log.info("names={}", names);
        tableModel.setColumnIdentifiers(names);

        resetColumnWidth();
        setData(new ArrayList<>(modelData));
    }

    /**
     * 使用场景:
     * .如 {@link #setData(List)}时 内部调用
     * .[监听单元格更新]后,手动调用
     */
    public void updateSummary() {
        for (VColumnInfo column : visibleColumns) {
            String summaryText = ViewTableHeader.getSummaryText(column, modelData);
            column.setSummaryText(summaryText);
        }
        getTableHeader().repaint();
    }

    /**
     * 代码直接更新了model
     *
     * @param rowIndex modeIndex
     */
    @SuppressWarnings("unchecked")
    public void notifyUpdated(int rowIndex) {
        JSONObject json = (JSONObject) JSON.toJSON(getModelData().get(rowIndex));
        //rowIndex = convertRowIndexToView(rowIndex);//不需要转!!
        Vector<Object> row = (Vector<Object>) tableModel.getDataVector().get(rowIndex);
        for (int i = 0; i < visibleColumns.size(); i++) {
            VColumnInfo columnInfo = visibleColumns.get(i);
            if (!columnInfo.isIndexColumn()) {
                Object value = json.get(columnInfo.getFieldName());
                row.set(i, value);
            }
        }
        repaint();
    }

    //===
    private class MyTableModel extends DefaultTableModel {

        private MyTableModel() {
            super(visibleColumns.stream()
                            .map(VColumnInfo::getName)
                            .collect(Collectors.toCollection(Vector::new)),
                    0);
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return visibleColumns.get(columnIndex).getClz();
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return visibleColumns.get(column).isEditable();
        }

        public void setData(List<R> list) {
            //log.info("setData1");
            modelData.clear();
            modelData.addAll(list);

            dataVector.clear();

            //log.info("setData2");
            if (list.isEmpty()) {
                fireTableDataChanged();
                updateSummary();
                return;
            }

            //dataVector: 每个元素代表一行, 类型是Vector,
            JSONArray array = (JSONArray) JSON.toJSON(list);
            List<Vector<Object>> rowList = new ArrayList<>();
            for (int i = 0; i < array.size(); i++) {
                Object json = array.get(i);

                //每行是Vector,按序 add(value)
                Vector<Object> rowData = new Vector<>();
                for (VColumnInfo columnInfo : visibleColumns) {
                    if (columnInfo.isIndexColumn()) {
                        //不兼容[服务端排序]
                        rowData.add(i + 1);
                    } else {
                        Object value = ((JSONObject) json).get(columnInfo.getFieldName());
                        rowData.add(value);
                    }
                }
                rowList.add(rowData);
            }

            //noinspection unchecked
            dataVector.addAll(rowList);
            //log.info("setData3");
            fireTableDataChanged();
            //log.info("setData4");
            updateSummary();
        }
    }

    /**
     * 输入监听
     */
    public interface ValueUpdateListener<R> {
        /**
         * @param row modelIndex
         */
        void afterUpdate(int row, int col, Object newValue, Object oldValue, ViewTable<R> table);
    }

}
